#ifndef __GLF_CORE_INTRINSIC_SCALAR_H_INCLUDED__
#define __GLF_CORE_INTRINSIC_SCALAR_H_INCLUDED__

struct f32x4
{ 
public:
	float& operator[] (int i) { return v[i]; }
	const float& operator[] (int i) const { return v[i]; }
private:
	float v[4];
};

namespace glf
{
namespace simd
{

namespace detail
{

template <typename TOperator>
struct applyop
{
	template <typename T>
	T operator()(const T& a)
	{
		TOperator op;
		T out;
		out[0] = op(a[0]);
		out[1] = op(a[1]);
		out[2] = op(a[2]);
		out[3] = op(a[3]);
		return out;
	}

	template <typename T>
	T operator()(const T& a, const T& b)
	{
		TOperator op;
		T out;
		out[0] = op(a[0], b[0]);
		out[1] = op(a[1], b[1]);
		out[2] = op(a[2], b[2]);
		out[3] = op(a[3], b[3]);
		return out;
	}

	template <typename T>
	T operator()(const T& a, const T& b, const T& c)
	{
		TOperator op;
		T out;
		out[0] = op(a[0], b[0], c[0]);
		out[1] = op(a[1], b[1], c[1]);
		out[2] = op(a[2], b[2], c[2]);
		out[3] = op(a[3], b[3], c[3]);
		return out;
	}
};

struct addop
{
	f32 operator()(f32 a, f32 b)
	{
		return a + b;
	}
};

struct subop
{
	f32 operator()(f32 a, f32 b)
	{
		return a - b;
	}
};

struct divop
{
	float operator()(float a, float b)
	{
		return a / b;
	}
};

struct mulop
{
	float operator()(float a, float b)
	{
		return a * b;
	}
};

struct muladdop
{
	float operator()(float a, float b, float c)
	{
		return a + b * c;
	}
};

struct xorop
{
	s32 operator()(s32 a, s32 b)
	{
		return a ^ b;
	}
};

struct sqrt
{
	float operator()(float a)
	{
		return ::sqrt(a);
	}
};

} // namespace detail

inline
f32x4 add(const f32x4& a, const f32x4& b)
{
	detail::applyop<detail::addop> op;
	return op(a, b);
}

inline
f32x4 sub(const f32x4& a, const f32x4& b)
{
	detail::applyop<detail::subop> op;
	return op(a, b);
}

inline
f32x4 div(const f32x4& a, const f32x4& b)
{
	detail::applyop<detail::divop> op;
	return op(a, b);
}

inline
f32x4 mul(const f32x4& a, const f32x4& b)
{
	detail::applyop<detail::mulop> op;
	return op(a, b);
}

inline
f32x4 mul(const f32x4& a, float b)
{
	f32x4 out;
	out[0] = a[0] * b;
	out[1] = a[1] * b;
	out[2] = a[2] * b;
	out[3] = a[3] * b;
	return out;
}

inline
f32x4 mul(float a, const f32x4& b)
{
	return mul(b, a);
}

inline
f32x4 muladd(const f32x4& a, const f32x4& b, const f32x4& c)
{
	detail::applyop<detail::muladdop> op;
	return op(a, b, c);
}

inline
f32x4 muladd(const f32x4& a, float b, const f32x4& c)
{
	f32x4 out;
	out[0] = a[0] + b * c[0];
	out[1] = a[1] + b * c[1];
	out[2] = a[2] + b * c[2];
	out[3] = a[3] + b * c[3];
	return out;
}

inline
f32x4 muladd(const f32x4& a, const f32x4& b, float c)
{
	return muladd(a, c, b);
}

inline
f32x4 sqrt(const f32x4& a)
{
	detail::applyop<detail::sqrt> op;
	return op(a);
}

template <int srcmask, int destmask>
inline
f32x4 dotproduct(const f32x4& a, const f32x4& b)
{
	f32 sum = 0.0f;
	sum += (srcmask & 0x1 ? a[0] * b[0] : 0.0f);
	sum += (srcmask & 0x2 ? a[1] * b[1] : 0.0f);
	sum += (srcmask & 0x4 ? a[2] * b[2] : 0.0f);
	sum += (srcmask & 0x8 ? a[3] * b[3] : 0.0f);
	f32x4 out;
	out[0] = (destmask & 0x1 ? sum : 0.0f);
	out[1] = (destmask & 0x2 ? sum : 0.0f);
	out[2] = (destmask & 0x4 ? sum : 0.0f);
	out[3] = (destmask & 0x8 ? sum : 0.0f);
	return out;
}

template <int srcmask>
inline
void dotproduct(const f32x4& a, const f32x4& b, float& out)
{
	out = 0.0f;
	out += (srcmask & 0x1 ? a[0] * b[0] : 0.0f);
	out += (srcmask & 0x2 ? a[1] * b[1] : 0.0f);
	out += (srcmask & 0x4 ? a[2] * b[2] : 0.0f);
	out += (srcmask & 0x8 ? a[3] * b[3] : 0.0f);
}

inline 
f32x4 exor(const f32x4& a, const f32x4& b)
{
	f32x4 tmp;
	unsigned int term = *(unsigned int*)&a[0] ^ *(unsigned int*)&b[0];
	tmp[0] = *(float*)&term;
	term = *(unsigned int*)&a[1] ^ *(unsigned int*)&b[1];
	tmp[1] = *(float*)&term;
	term = *(unsigned int*)&a[2] ^ *(unsigned int*)&b[2];
	tmp[2] = *(float*)&term;
	term = *(unsigned int*)&a[3] ^ *(unsigned int*)&b[3];
	tmp[3] = *(float*)&term;
	return tmp;
}

inline
void load(f32 v, f32x4& out)
{
	out[0] = out[1] = out[2] = out[3] = v;
}

inline
f32x4 loadr(float x, float y, float z, float w)
{
	// Little endian storage with w being the lowest term
	f32x4 out;
	out[0] = x;
	out[1] = y;
	out[2] = z;
	out[3] = w;
	return out;
}

inline
f32x4 load(float x, float y, float z, float w)
{
	// Little endian storage with w being the lowest term
	f32x4 out;
	out[3] = x;
	out[2] = y;
	out[1] = z;
	out[0] = w;
	return out;
}

inline
void loadu(const float* v, f32x4& out)
{
	out[0] = v[0];
	out[1] = v[1];
	out[2] = v[2];
	out[3] = v[3];
}

inline
void loada(const float* v, f32x4& out)
{
	loadu(v, out);
}

inline
void store(const f32x4& v, float& out)
{
	out = v[0];
}

inline
void storeu(const f32x4& v, float* out)
{
	out[0] = v[0];
	out[1] = v[1];
	out[2] = v[2];
	out[3] = v[3];
}

inline
void storea(const f32x4& v, float* out)
{
	storeu(v, out);
}

} // namespace simd
} // namespace glf

#endif // __GLF_CORE_INTRINSIC_SCALAR_H_INCLUDED__